﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel;
using SharpDX.XAudio2;

namespace ScheduledXAudio2
{
    public static class XA2IOFront
    {
        #region Fields
        public const int SampleRate = 88200;
        internal static List<SubmixVoice> SubmixerList;
        internal static List<AudioBuffer> BufferList;
        public static ObservableCollection<DrumPatchInstance> Instances = new ObservableCollection<DrumPatchInstance>();
        internal static ObservableCollection<PatchHost> Hosts = new ObservableCollection<PatchHost>();

        #region Private Fields
        // Access to the global state is guarded by lock(staticLock).
        private static Object staticLock = new Object();
        private static XAudio2 Device;
        private static MasteringVoice Master;
        private static Clock Clock;
        private static bool isReceiving;
        private static int VoicesAllowedToStackUntilDisposal = 64;
        #endregion

        #endregion

        #region Properties
        public static Clock Scheduler 
        { 
            get 
            {
                if (Clock == null)
                    return null;
                lock (staticLock)
                if (!Clock.IsRunning)
                {
                    Clock.Start();
                }
                    return Clock;
                }
            }

        public static bool IsReceiving
        {
            get { return isReceiving; }
        }
        public static bool IsRunning { get; set; }
        public static INotifyPropertyChanged OnOffNotifier;
        public static INotifyPropertyChanged BPMChangeNotifier;
        public static INotifyPropertyChanged RhythmSettingsChangedNotifier;

        /// <summary>
        /// The XAudio2 device.
        /// </summary>     
        public static XAudio2 DeviceAccessor
        {
            get
            {

                return Device;
            }
        }
        public static MasteringVoice MasterAccessor
        {
            get
            {
                if (Master == null)
                {
                    Master = new MasteringVoice(Device);
                }
                return Master;
            }
        }
        
        internal static List<SubmixVoice> Submixers
        {
            get
            {
                if (SubmixerList == null)
                {
                    SubmixerList = new List<SubmixVoice>();
                }
                return SubmixerList;
            }
        }
        internal static List<AudioBuffer> AudioBuffers
        {
            get
            {
                if (BufferList == null)
                {
                    BufferList = new List<AudioBuffer>();
                }
                return BufferList;
            }
        }

        #endregion

        #region Constructor

        static XA2IOFront()
        {
            Device = new XAudio2();
            //Device.StartEngine();
            Master = new MasteringVoice(Device, 2, SampleRate);

            Instances.CollectionChanged += Instances_CollectionChanged;
            Device.ProcessingPassEnd += Device_ProcessingPassEnd;
        }

       

       

        
        
        #endregion

        #region Public Methods       
        public static void StartReceiving(Clock clock)
        {
            lock (staticLock)
            {
                isReceiving = true;
                Clock = clock;
            }
        }
        public static void StopReceiving()
        {
            lock (staticLock)
            {
                Clock = null;
                isReceiving = false;
            }
        }
        public static void RegisterOnOffNotifier(INotifyPropertyChanged notifier)
        {
            OnOffNotifier = notifier;
            OnOffNotifier.PropertyChanged += new PropertyChangedEventHandler(OnOffNotifier_PropertyChanged);
        }
        public static void RegisterBPMChangeNotifier(INotifyPropertyChanged notifier)
        {
            BPMChangeNotifier = notifier;
            BPMChangeNotifier.PropertyChanged += new PropertyChangedEventHandler(BPMChangeNotifier_PropertyChanged);
        }
        public static void RegisterRhythmSettingsChangedNotifier(INotifyPropertyChanged notifier)
        {
            RhythmSettingsChangedNotifier = notifier;
            RhythmSettingsChangedNotifier.PropertyChanged += new PropertyChangedEventHandler(RhythmSettingsChangedNotifier_PropertyChanged);
        }
        #endregion Public Methods

        #region Internal Static Methods
        /// <summary>
        /// Releases all XA2 resources.
        /// </summary>
        internal static void Shutdown(bool restart)
        {
            Device.StopEngine();
            Master.Dispose();
            Device.Dispose();
            List<SubmixVoice> temp = new List<SubmixVoice>(Submixers);
            foreach (SubmixVoice smv in temp)
            {
                smv.Dispose();
            }
            if (restart)
            {
                Device = new XAudio2();
                Master = new MasteringVoice(Device, 2, SampleRate);
                Device.StartEngine();
            }
        }
        internal static void Initialize()
        {
            Device = new XAudio2();
            Device.StartEngine();

            Master = new MasteringVoice(Device);
        }
        /// <summary>
        /// Registers a submixer with the system.
        /// 
        /// </summary>
        /// <param name="master"></param>
        internal static void AddSubmixVoice(SubmixVoice submixvoice)
        {
            Submixers.Add(submixvoice);
        }

        internal static void AddInstance(DrumPatchInstance instance)
        {
            Instances.Add(instance);
        }

        internal static void AddHost(PatchHost host)
        {
        Hosts.Add(host);
        }
        /// <summary>
        /// Disposes any sound instance that has stopped playing.
        /// On a NullReferenceException the number of sound instances
        /// allowed to "pile" up before disposal is increased.
        /// </summary>
        internal static void Update()
        {            
            List<DrumPatchInstance> temp = new List<DrumPatchInstance>(Instances);
            foreach (DrumPatchInstance d in temp)
            {
                lock (staticLock)
                    if (!d.Playing)
                    {
                    try
                        {
                            Instances.Remove(d);
                            d.Dispose();
                        }
                    catch (NullReferenceException)
                        {
                            if (VoicesAllowedToStackUntilDisposal < 500)
                            {
                                VoicesAllowedToStackUntilDisposal = (int)Math.Round((VoicesAllowedToStackUntilDisposal * 1.5f), 0);
                            }
                            else
                            {
                                VoicesAllowedToStackUntilDisposal = (int)Math.Round((VoicesAllowedToStackUntilDisposal * 1.2f), 0);
                            }
                            return ;
                        }                 
                    }
            }            
        }
        internal static void DisposeHosts()
        {
            List<PatchHost> temp = new List<PatchHost>(Hosts);
            foreach (PatchHost ph in temp)
            {
                ph.Dispose();
                temp.Remove(ph);
            }
        }
        public static void DisposeAHost(PatchHost host)
        {
            ForcedCleanup(false);
            host.Dispose();
            List<PatchHost> temp = new List<PatchHost>(Hosts);
            temp.Remove(host);
        }
       
        public static void ForcedCleanup(bool shutdown)
        {
            //StopReceiving();
            
            Device.StopEngine();
                lock (staticLock)
                {
                    foreach (DrumPatchInstance d in (new List<DrumPatchInstance>(Instances)))
                {
                    if (d!=null&&!d.Playing)
                    {
                        try
                        {
                            d.Dispose();
                        }
                        catch (NullReferenceException)
                        {
                            break;
                        }                       
                    }
                }
                if (!shutdown)
                {
                    //StartReceiving(Scheduler);
                    Device.StartEngine();
                }
            }
        }
        #endregion

        #region Event Handlers
        static void Device_ProcessingPassEnd(object sender, EventArgs e)
        {
            Console.WriteLine("process pass ending");
        }

        static void RhythmSettingsChangedNotifier_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {

        }
        static void BPMChangeNotifier_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {

        }
        static void OnOffNotifier_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (IsRunning)
            {
                case true:
                    Clock.Instance.Stop();
                    IsRunning = false;
                    ForcedCleanup(IsRunning);
                    break;
                case false:
                    IsRunning = true;
                    Device.StartEngine();
                    if (!IsReceiving) 
                        StartReceiving(Clock.Instance); 
                    ClockStartMessage starter = new ClockStartMessage(Scheduler, Scheduler.Time);
                    break;
            }
        }
        static void Instances_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (Instances.Count > VoicesAllowedToStackUntilDisposal)
            {
                Update();
            }
        }
        #endregion
    }
}